<!DOCTYPE html>
<!--

hypotrochoid.html

hypotrochoid Thought Patch

Copyright (C) 2010, Chris Allert, All rights reserved.

This program is free software; you can redistribute it and/or modify it under 
the terms of the GNU General Public License as published by the Free Software 
Foundation; either version 3 of the License, or (at your option) any later 
version.

This program is distributed in the hope that it will be useful, but WITHOUT ANY 
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 
PARTICULAR PURPOSE. See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with 
this program; if not, write to the Free Software Foundation, Inc., 59 Temple 
Place, Suite 330, Boston, MA 02111-1307 USA

The latest version of this file can be downloaded from
http://thought-patches.sourceforge.net/

-->
<!--

hypotrochoid equations:

x_theta = (R - r) * cos(theta) + d * cos(theta * (R-r)/r)
y_theta = (R - r) * sin(theta) - d * sin(theta * (R-r)/r)

epitrochoid equations:

x_theta = (R + r) * cos(theta) - d * cos(theta * (R+r)/r)
y_theta = (R + r) * sin(theta) - d * sin(theta * (R+r)/r)


-->
<html manifest="hypotrochoid.manifest">
	<head>
		<title>hypotrochoid</title>
<meta http-equiv="X-UA-Compatible" content="chrome=1">
<link rel="apple-touch-icon" href="hypotrochoid.png">
<link rel="apple-touch-startup-image" href="hypotrochoid-startup.png">
<meta content="width=device-width; initial-scale=1.0;  maximum-scale=1.0; user-scalable=0;" name="viewport" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black" />
<meta name="format-detection" content="telephone=no" />
<style>
	* {
		font-family: sans-serif;
		-webkit-user-select: none;
	}
	body { 
		padding:0px; 
		margin:0px; 
		color: white; 
		background-color: black; 
		font-size: 16px;
		overflow: hidden;
	}
	input { 
		font-size:16px;
		padding: 0px;
	}
	select { 
		font-size:16px;
		padding: 0px;
	}
	form { padding:0px; margin:0px; }
</style>
		<script src="../../lib/contrib/jsbn/jsbn.js"></script>
		<script src="../../lib/contrib/jsbn/jsbn2.js"></script>
		<script src="../../lib/big-number.js"></script>
		<script src="../../lib/form.js"></script>
		<script src="../../lib/canvas.js"></script>
		<script src="../../lib/query.js"></script>
		<script>
			//var message;

			var n_R;
			var n_r;
			var n_d;

			var animate_curve;

			var animation = new Object();
			var fixed = null;

			function animate_epitrochoid() {
				if (null != animation.interval) {
					clearInterval(animation.interval);
				}

				/*
				the width will be 
				max(
					2 * R,
					2 * R - r + abs(d)
				)

				so we want to scale everything to fit
				in and fill the space we have.

				for now we'll draw lines to connect the
				sample points.  eventually we'll want
				to connect them with curves
				*/

				var R = n_R.value.clone();
				var r = n_r.value.clone();
				var d = n_d.value.clone();

				// first we want to scale everything down.

				var g = d.gcd(r.gcd(R));
				R = R.divide(g);
				r = r.divide(g);
				d = d.divide(g);

				/*

				the number of rotations we need
				to generate a complete pattern is

				r / gcd(r, R)

				*/

				var sym = parseInt(R.divide(R.gcd(r)));
				var rot = parseInt(r.divide(R.gcd(r)));
				R = parseInt(R);
				r = parseInt(r);
				d = parseInt(d);

				var w = 2 * Math.abs(R + r + d);

				var theta = (rot * Math.PI) / sym;
				w = Math.max( (R + r), Math.sqrt(
				Math.pow((
					(R + r) * Math.cos(theta) - 
					d * Math.cos(theta * (R+r)/r)
				), 2) + Math.pow((
					(R + r) * Math.sin(theta) - 
					d * Math.sin(theta * (R+r)/r)
				), 2)
				));

				w *= 2;

				var draw_scale = Math.min(
				canvas.width, canvas.height);
				draw_scale -= 20;
				draw_scale /= w;

				var step = 64;
				var samples = step;
				samples *= sym;
				samples *= rot;

				var dot = new_draw_dot();
				dot.r = 2;
				var line = new_draw_line();
				var c_x = canvas.width / 2;
				var c_y = canvas.height / 2;

				canvas_clear();
				ctx.globalCompositeOperation = 'lighter';
				var x_prev;
				var y_prev;

				animation.R = R;
				animation.r = r;
				animation.d = d;
				animation.rot = rot;
				animation.sym = sym;
				animation.draw_scale = draw_scale;
				animation.x_prev = 0;
				animation.y_prev = 0;
				animation.c_x = c_x;
				animation.c_y = c_y;
				animation.dot = dot;
				animation.line = line;
				animation.i = 0;
				animation.samples = samples;
				animation.step = step;

				animation.interval = 
					setInterval("animate_epitrochoid_steps()", 100);

			}
			
			function animate_epitrochoid_steps() {
				var a = animation;
				for (var i = 0; i < a.step; ++i) {
					if (a.i <= a.samples) {
						animate_epitrochoid_step();
					}
				}
				if (a.i > a.samples) {
					clearInterval(a.interval);
				}
			}

			function animate_epitrochoid_step() {
				var a = animation;
				var n = a.i/a.samples;
				theta = n * a.rot * Math.PI * 2;
				var x_theta = 
					(a.R + a.r) * Math.cos(theta) - 
					a.d * Math.cos(theta * (a.R+a.r)/a.r);

				var y_theta = 
					(a.R + a.r) * Math.sin(theta) - 
					a.d * Math.sin(theta * (a.R+a.r)/a.r);

				x_theta *= a.draw_scale;
				y_theta *= a.draw_scale;
				x_theta += a.c_x;
				y_theta += a.c_y;

				if (a.i > 0) {
					a.line.xa = a.x_prev;
					a.line.xb = x_theta;
					a.line.ya = a.y_prev;
					a.line.yb = y_theta;
					a.line.clr = rainbow_color(n);
					draw_line(a.line);
					/*
					a.dot.x = x_theta;
					a.dot.y = y_theta;
					a.dot.clr = rainbow_color(n + .5);
					draw_dot(a.dot);
					*/
				}
				a.x_prev = x_theta;
				a.y_prev = y_theta;
				++a.i;
			}

			function animate_hypotrochoid() {
				if (null != animation.interval) {
					clearInterval(animation.interval);
				}

				/*
				the width will be 
				max(
					2 * R,
					2 * R - r + abs(d)
				)

				so we want to scale everything to fit
				in and fill the space we have.

				for now we'll draw lines to connect the
				sample points.  eventually we'll want
				to connect them with curves
				*/

				var R = n_R.value.clone();
				var r = n_r.value.clone();
				var d = n_d.value.clone();

				// first we want to scale everything down.

				var g = d.gcd(r.gcd(R));
				R = R.divide(g);
				r = r.divide(g);
				d = d.divide(g);

				/*

				the number of rotations we need
				to generate a complete pattern is

				r / gcd(r, R)

				*/

				var sym = parseInt(R.divide(R.gcd(r)));
				var rot = parseInt(r.divide(R.gcd(r)));
				R = parseInt(R);
				r = parseInt(r);
				d = parseInt(d);

				var w = 2 * Math.abs(R + r + d);

				var theta = (rot * Math.PI) / sym;
				if (R > r) {
					w = R - r + d
				} else {
					w = Math.max( (R - r + d), Math.sqrt(
					Math.pow((
						(R - r) * Math.cos(theta) + 
						d * Math.cos(theta * (R-r)/r)
					), 2) + Math.pow((
						(R - r) * Math.sin(theta) - 
						d * Math.sin(theta * (R-r)/r)
					), 2)
					));
				}

				w *= 2;

				var draw_scale = Math.min(
				canvas.width, canvas.height);
				draw_scale -= 20;
				draw_scale /= w;

				var step = 64;
				var samples = step;
				samples *= sym;
				samples *= rot;

				var dot = new_draw_dot();
				dot.r = 2;
				var line = new_draw_line();
				var c_x = canvas.width / 2;
				var c_y = canvas.height / 2;

				canvas_clear();
				ctx.globalCompositeOperation = 'lighter';
				var x_prev;
				var y_prev;

				animation.R = R;
				animation.r = r;
				animation.d = d;
				animation.rot = rot;
				animation.sym = sym;
				animation.draw_scale = draw_scale;
				animation.x_prev = 0;
				animation.y_prev = 0;
				animation.c_x = c_x;
				animation.c_y = c_y;
				animation.dot = dot;
				animation.line = line;
				animation.i = 0;
				animation.samples = samples;
				animation.step = step;

				animation.interval = 
					setInterval("animate_hypotrochoid_steps()", 100);

			}

			function animate_hypotrochoid_steps() {
				var a = animation;
				for (var i = 0; i < a.step; ++i) {
					if (a.i <= a.samples) {
						animate_hypotrochoid_step();
					}
				}
				if (a.i > a.samples) {
					clearInterval(a.interval);
				}
			}

			function animate_hypotrochoid_step() {
				var a = animation;
				var n = a.i/a.samples;
				theta = n * a.rot * Math.PI * 2;
				var x_theta = 
					(a.R - a.r) * Math.cos(theta) + 
					a.d * Math.cos(theta * (a.R-a.r)/a.r);

				var y_theta = 
					(a.R - a.r) * Math.sin(theta) - 
					a.d * Math.sin(theta * (a.R-a.r)/a.r);

				x_theta *= a.draw_scale;
				y_theta *= a.draw_scale;
				x_theta += a.c_x;
				y_theta += a.c_y;

				if (a.i > 0) {
					a.line.xa = a.x_prev;
					a.line.xb = x_theta;
					a.line.ya = a.y_prev;
					a.line.yb = y_theta;
					a.line.clr = rainbow_color(n);
					draw_line(a.line);
					/*
					a.dot.x = x_theta;
					a.dot.y = y_theta;
					a.dot.clr = rainbow_color(n + .5);
					draw_dot(a.dot);
					*/
				}
				a.x_prev = x_theta;
				a.y_prev = y_theta;
				++a.i;
			}

			function changed_something() {
				if (null == animate_curve) { return; }
				animate_curve();
			}

			function read_query_string() {
				query_init();
				if (
					(null != query['w']) && 
					(null != query['h'])
				) { if (null == fixed) {
					canvas.addEventListener('mousedown', show_menu, false);
					fixed = {
						w: query['w'],
						h: query['h']
					};
				} }
				if (null != query['big_r']) {
					n_R.change_value(query['big_r'][0]);
				}
				if (null != query['little_r']) {
					n_r.change_value(query['little_r'][0]);
				}
				if (null != query['d']) {
					n_d.change_value(query['d'][0]);
				}
			}

			function hide_menu() {
				document.getElementById('menu').style.visibility = 'hidden';
			}

			function show_menu() {
				if (null != fixed) {
					document.getElementById('menu').style.visibility = 'visible';
				}
			}

			function on_resize() {
				var e = document.getElementById('form_div');
				if (null == fixed) {
					canvas.width = window.innerWidth; 
					canvas.height = window.innerHeight - e.clientHeight;
				} else {
					canvas.width = fixed.w;
					canvas.height= fixed.h;
					document.body.style.backgroundColor = 'gray';
				}
				e.style.top = ((window.innerHeight - e.clientHeight)) + 'px';
				e.style.left= ((window.innerWidth - e.clientWidth) / 2) + 'px';
				ctx.globalCompositeOperation = 'lighter';
				changed_something();
			}

			function init() { 
				document.getElementById('submit_button').disabled = !navigator.onLine;
				canvas_init();
				animate_curve = animate_hypotrochoid;

				//message = document.getElementById('message');

				var v_r = 1 + Math.floor(49 * Math.random());
				var v_d = 1 + Math.floor(49 * Math.random());
				var v_R = v_r;
				while (v_r == v_R) {
					v_R = 1 + Math.floor(49 * Math.random());
				}
				n_R = new NumberSpinner({
					select: document.getElementById('big_r'),
					min: 1,
					value: v_R,
					page_size: 100
				});
				n_R.update();
				n_R.callback = changed_something;

				n_r = new NumberSpinner({
					select: document.getElementById('little_r'),
					min: 1,
					value: v_r,
					page_size: 100
				});
				n_r.update();
				n_r.callback = changed_something;

				n_d = new NumberSpinner({
					select: document.getElementById('d'),
					min: 1,
					value: v_d,
					page_size: 100
				});
				n_d.update();
				n_d.callback = changed_something;

				read_query_string();
				window.onresize = on_resize;
				on_resize();

			}

		</script>
	</head>
	<body onLoad="init()">
	<div id="canvas_div" style="position: absolute; "><canvas id="canvas" class="canvas"  width="320" height="300">This site does not work in your browser.<br /><a href="http://code.google.com/chrome/chromeframe/">click here to install</a></canvas></div>
	<div id="form_div" style="position: absolute; ">
		<form action="../share/share.html" method="get">
			<label for="big_r">R</label>
			<select id="big_r" name="big_r" onChange="number_spinner_callback(this)"><option></option></select>
			<label for="little_r">r</label>
			<select id="little_r" name="little_r" onChange="number_spinner_callback(this)"><option></option></select>
			<label for="d">d</label>
			<select id="d" name="d" onChange="number_spinner_callback(this)"><option></option></select>
			<input type="hidden" name="patch" value="hypotrochoid" />
			<input id="submit_button" type="submit" value="share" />
		</form>
		<!--<div id="message"></div>-->
	</div>
	<div id="menu" style="visibility: hidden; position: absolute; ">
		<input type="button" value="download image" onClick="canvas_save(), hide_menu()" />
		<input type="button" value="cancel" onClick="hide_menu()" />
	</div>
	</body>
</html>
